package org.jeecg.weixin.common.session;

import org.jeecg.weixin.common.util.res.StringManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 基于内存的session manager.
 *
 * @author Daniel Qian
 */
public class StandardSessionManager implements WxSessionManager, InternalSessionManager {
	protected static final StringManager SM = StringManager.getManager(Constants.PACKAGE);
	/**
	 * The descriptive name of this Manager implementation (for logging).
	 */
	private static final String name = "SessionManagerImpl";
	protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class);
	private final Object maxActiveUpdateLock = new Object();
	/**
	 * 后台清理线程是否已经开启
	 */
	private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false);

	// -------------------------------------- InternalSessionManager
	/**
	 * The set of currently active Sessions for this Manager, keyed by session
	 * identifier.
	 */
	protected Map<String, InternalSession> sessions = new ConcurrentHashMap<>();
	/**
	 * The maximum number of active Sessions allowed, or -1 for no limit.
	 */
	protected int maxActiveSessions = -1;

	/**
	 * Number of session creations that failed due to maxActiveSessions.
	 */
	protected int rejectedSessions = 0;

	/**
	 * The default maximum inactive interval for Sessions created by this Manager.
	 */
	protected int maxInactiveInterval = 30 * 60;

	/**
	 * Number of sessions created by this manager
	 */
	protected long sessionCounter = 0;

	protected volatile int maxActive = 0;
	/**
	 * Processing time during session expiration.
	 */
	protected long processingTime = 0;
	/**
	 * Frequency of the session expiration, and related manager operations. Manager
	 * operations will be done once for the specified amount of backgrondProcess
	 * calls (ie, the lower the amount, the most often the checks will occur).
	 */
	protected int processExpiresFrequency = 6;
	/**
	 * background processor delay in seconds
	 */
	protected int backgroundProcessorDelay = 10;
	/**
	 * Iteration count for background processing.
	 */
	private int count = 0;

	@Override
	public WxSession getSession(String sessionId) {
		return getSession(sessionId, true);
	}

	@Override
	public WxSession getSession(String sessionId, boolean create) {
		if (sessionId == null) {
			throw new IllegalStateException(SM.getString("sessionManagerImpl.getSession.ise"));
		}

		InternalSession session = findSession(sessionId);
		if ((session != null) && !session.isValid()) {
			session = null;
		}
		if (session != null) {
			session.access();
			return session.getSession();
		}

		// Create a new session if requested and the response is not committed
		if (!create) {
			return (null);
		}

		session = createSession(sessionId);

		if (session == null) {
			return null;
		}

		session.access();
		return session.getSession();
	}

	@Override
	public void remove(InternalSession session) {
		remove(session, false);
	}

	@Override
	public void remove(InternalSession session, boolean update) {
		if (session.getIdInternal() != null) {
			this.sessions.remove(session.getIdInternal());
		}
	}

	@Override
	public InternalSession findSession(String id) {
		if (id == null) {
			return (null);
		}
		return this.sessions.get(id);
	}

	@Override
	public InternalSession createSession(String sessionId) {
		if (sessionId == null) {
			throw new IllegalStateException(SM.getString("sessionManagerImpl.createSession.ise"));
		}

		if ((this.maxActiveSessions >= 0) && (getActiveSessions() >= this.maxActiveSessions)) {
			this.rejectedSessions++;
			throw new TooManyActiveSessionsException(SM.getString("sessionManagerImpl.createSession.tmase"),
					this.maxActiveSessions);
		}

		// Recycle or create a Session instance
		InternalSession session = createEmptySession();

		// Initialize the properties of the new session and return it
		session.setValid(true);
		session.setCreationTime(System.currentTimeMillis());
		session.setMaxInactiveInterval(this.maxInactiveInterval);
		session.setId(sessionId);
		this.sessionCounter++;

		return session;
	}

	@Override
	public int getActiveSessions() {
		return this.sessions.size();
	}

	@Override
	public InternalSession createEmptySession() {
		return (getNewSession());
	}

	/**
	 * Get new session class to be used in the doLoad() method.
	 */
	protected InternalSession getNewSession() {
		return new StandardSession(this);
	}

	@Override
	public void add(InternalSession session) {
		// 当第一次有session创建的时候，开启session清理线程
		if (!this.backgroundProcessStarted.getAndSet(true)) {
			Thread t = new Thread(() -> {
				while (true) {
					try {
						// 每秒清理一次
						Thread.sleep(StandardSessionManager.this.backgroundProcessorDelay * 1000L);
						backgroundProcess();
					} catch (InterruptedException e) {
						Thread.currentThread().interrupt();
						StandardSessionManager.this.log.error("SessionManagerImpl.backgroundProcess error", e);
					}
				}
			});
			t.setDaemon(true);
			t.start();
		}

		this.sessions.put(session.getIdInternal(), session);
		int size = getActiveSessions();
		if (size > this.maxActive) {
			synchronized (this.maxActiveUpdateLock) {
				if (size > this.maxActive) {
					this.maxActive = size;
				}
			}
		}

	}

	/**
	 * Return the set of active Sessions associated with this Manager. If this
	 * Manager has no active Sessions, a zero-length array is returned.
	 */
	@Override
	public InternalSession[] findSessions() {

		return this.sessions.values().toArray(new InternalSession[0]);

	}

	@Override
	public void backgroundProcess() {
		this.count = (this.count + 1) % this.processExpiresFrequency;
		if (this.count == 0) {
			processExpires();
		}
	}

	/**
	 * Invalidate all sessions that have expired.
	 */
	public void processExpires() {

		long timeNow = System.currentTimeMillis();
		InternalSession sessions[] = findSessions();
		int expireHere = 0;

		if (this.log.isDebugEnabled()) {
			this.log.debug("Start expire sessions {} at {} sessioncount {}", getName(), timeNow, sessions.length);
		}
		for (InternalSession session : sessions) {
			if (session != null && !session.isValid()) {
				expireHere++;
			}
		}
		long timeEnd = System.currentTimeMillis();
		if (this.log.isDebugEnabled()) {
			this.log.debug("End expire sessions {} processingTime {} expired sessions: {}", getName(),
					timeEnd - timeNow, expireHere);
		}
		this.processingTime += (timeEnd - timeNow);

	}

	@Override
	public void setMaxInactiveInterval(int interval) {

		this.maxInactiveInterval = interval;

	}

	/**
	 * Set the manager checks frequency.
	 *
	 * @param processExpiresFrequency
	 *            the new manager checks frequency
	 */
	@Override
	public void setProcessExpiresFrequency(int processExpiresFrequency) {

		if (processExpiresFrequency <= 0) {
			return;
		}

		this.processExpiresFrequency = processExpiresFrequency;

	}

	@Override
	public void setBackgroundProcessorDelay(int backgroundProcessorDelay) {
		this.backgroundProcessorDelay = backgroundProcessorDelay;
	}

	/**
	 * Return the descriptive short name of this Manager implementation.
	 */
	public String getName() {

		return (name);

	}

	/**
	 * Set the maximum number of active Sessions allowed, or -1 for no limit.
	 *
	 * @param max
	 *            The new maximum number of sessions
	 */
	@Override
	public void setMaxActiveSessions(int max) {

		this.maxActiveSessions = max;

	}

}
